<script>

      // Community Detection Function

      function communityDetect (lougraph, grgraph, callback) {
 
                let node_data = lougraph.node_data;
                let edge_data = lougraph.edge_data;

                let nodeIDs_rev = graphFactory.getNodeIDsRev();
                let nodeIDs = graphFactory.getNodeIDs();

                let totaledgescount = graphFactory.getStats('edgesUndirected');

                let current_context = userFactory.getCurrentContext();

                if (node_data.length > 0 && edge_data.length > 0) {

                    // Calculate Louvain community and modularity

                    let community = jLouvain().nodes(node_data).edges(edge_data);
                    let community_assignment_result = community();
                    let node_ids = Object.keys(community_assignment_result['communities']);
                    let com_ass = {};

                    for (let key in community_assignment_result['communities']) {
                        com_ass[nodeIDs_rev[key]] = community_assignment_result['communities'][key];
                    }
                    
                    console.log('Resulting Louvain Community Data', com_ass);
                    console.log('Final Louvain Modularity', community_assignment_result['modularity']);

                    // Get the graph back from Graphology with community attribute for nodes
                    let newgraph = louvain_process(grgraph);

                    // Assign community number to every node
                    let louvain_communities = {};
                    let graphology_communities = [];

                    grgraph.forEachNode((node, attributes) => {
                        let tempo_comm = grgraph.getNodeAttribute(node,'community');
                        if (graphology_communities.indexOf(tempo_comm) == -1) {
                            graphology_communities.push(tempo_comm);
                        }
                        louvain_communities[node] = graphology_communities.indexOf(tempo_comm);
                    });

                    // Let's calculate modularity for Graphology
                    let modularity_score = louvain_modularity(newgraph);

                    let com_ass2 = {};
                    for (var key in louvain_communities) {
                        com_ass2[nodeIDs_rev[key]] = louvain_communities[key];
                    }
                    console.log('Resulting Graphology Community Data', com_ass2);
                    console.log('Graphology modularity score:', modularity_score);

                    node_ids = Object.keys(louvain_communities);

                    let max_community_number = 0;

                    let top_communities = [];

                    let all_communities = [];

                    let totalnodescount = 0

                    // We now add the new community integer ID to all the nodes in the Sigma graph

                    sigma.instances(0).graph.nodes().forEach(function(n) {
                        if (!n.iscontext) {
                            n.community = louvain_communities[n.id];

                            graphFactory.saveCommunityOfNode(n);

                            var commune = n.community;
                            if (!top_communities[commune]) {
                                top_communities[commune] = [];
                                all_communities[commune] = {id: commune, length: 0, nodes: []};
                            }
                            

                            top_communities[commune].push({id: n.id, name: n.originalLabel, degree: sigma.instances(0).graph.degree(n.id), bc: bc_nodes_map[n.id]});
                            all_communities[commune].nodes.push({id: n.id, name: n.originalLabel, degree: sigma.instances(0).graph.degree(n.id), bc: bc_nodes_map[n.id]});
                            all_communities[commune].length += 1;

                            // number of communities
                            max_community_number = max_community_number < louvain_communities[n.id] ? louvain_communities[n.id]: max_community_number;

                            // number of nodes
                            totalnodescount += 1;
                        }

                    });

                    console.log('Total communities: ');
                    console.log(parseInt(max_community_number + 1));
                    
                    // Sort (and converts to array)
                   all_communities = _.sortBy(all_communities,'length').reverse();

                    // Sort all the nodes in every community by BC or degree influence

                    let most_influential = userFactory.getMostInfluential();
                    for (let j = 0; j < all_communities.length; j++) {
                        if (most_influential == 'bc2') {
                           all_communities[j].nodes = _.sortBy(all_communities[j].nodes,'bc').reverse();
                        }
                        else {
                           all_communities[j].nodes = _.sortBy(all_communities[j].nodes,'degree').reverse();
                        }
                    }


                    // Save into the global parameter, to be extracted later
                    graphFactory.saveAllCommunities(all_communities);

                    // How many communities will we show to the user
                    let coms_to_show = 4;                    
                    if (all_communities.length < coms_to_show) {
                        coms_to_show = all_communities.length;
                    }

                    // Cut out the first N nodes from every community and get the top N communities
                    let nodes_to_cut = 3;

                    let topN_communities = graphFactory.getAllCommunities(coms_to_show, nodes_to_cut);

                    // TODO might need to delete the topp_communities and most_inf_comm_nodes
                    let topp_communities = topN_communities;
                    
                    // Populate Analytics Panel with community information
                    analyticsPanel.populateCommunities(topN_communities);

                    // Get the top BC nodes
                    // TODO implement Jenks-Breaks to calculate the optimal number of nodes shown
                    let topInfluenceNodes = graphFactory.getTopBCNodes(4);

                    console.log('Top betweenness centrality nnodes:')
                    console.log(topInfluenceNodes);

                    analyticsPanel.populateInfluential(topInfluenceNodes);

                    // Send the default search setting to the Global Search
                    // TODO take into account the polysingularity index
                    globalSearch.populateGlobalSearch(topInfluenceNodes);
                    
                    // Set the number of nodes in the first and second communities
                    graphFactory.setStats('FirstComNodes',all_communities[0].length);
                    let nodesfirstcom = graphFactory.getStats('FirstComNodes');
                    
                    if (all_communities[1]) {
                        graphFactory.setStats('SecondComNodes',all_communities[1].length);
                        let nodessecondcom = graphFactory.getStats('SecondComNodes');
                    }

                    // Save total nodes number
                    graphFactory.setStats('TotalNodes', totalnodescount);

                    global_totalnodes = totalnodescount;



                    // Addcontext (added context) statistics: intersection and difference (in addcontext, but not in the original one)

                    let addcontext = userFactory.getAddContext();

                    let addContextIntersection = [];
                    let addContextDifference = [];

                    let addContextInt_nodes = [];
                    let addContextDiff_nodes = [];
                    let addContextAll_nodes = [];
                    let currentContextAll_nodes = [];

                    let addcontext_ratio = {};

                    let addcontext_nodes = {};

                    if (addcontext) {

                        let contextNodeID = graphFactory.getContextNodeIDs();
                        
                        addContextIntersection = _.intersection(contextNodeID[current_context],contextNodeID[addcontext]);
                        addContextDifference = _.difference(contextNodeID[addcontext],contextNodeID[current_context]);
                        

                        for (let node in contextNodeID[addcontext]) {
                            addContextAll_nodes.push({id: contextNodeID[addcontext][node], label: nodeIDs_rev[contextNodeID[addcontext][node]], size: graphFactory.getNodeSize(contextNodeID[addcontext][node])});
                        }

                        for (let node in contextNodeID[current_context]) {
                            currentContextAll_nodes.push({id: contextNodeID[current_context][node], label: nodeIDs_rev[contextNodeID[current_context][node]], size: graphFactory.getNodeSize(contextNodeID[current_context][node])});
                        }

                        for (let node in addContextIntersection) {
                            addContextInt_nodes.push({id: addContextIntersection[node], label: nodeIDs_rev[addContextIntersection[node]], size: graphFactory.getNodeSize(addContextIntersection[node])});
                        }

                        for (let node in addContextDifference) {
                            addContextDiff_nodes.push({id: addContextDifference[node], label: nodeIDs_rev[addContextDifference[node]], size: graphFactory.getNodeSize(addContextDifference[node])});
                        }

                        addContextInt_nodes = _.sortBy(addContextInt_nodes, 'size').reverse();
                        addContextDiff_nodes = _.sortBy(addContextDiff_nodes, 'size').reverse();

                        let addContextInt_count = addContextInt_nodes.length;
                        let addContextDiff_count = addContextDiff_nodes.length

                        graphFactory.setStats('AddContextIntersectionNodes', addContextInt_count);
                        graphFactory.setStats('AddContextDifferenceNodes', addContextDiff_count);

                        var addContextIntRatio = parseFloat(((addContextInt_count) / totalnodescount).toFixed(2));
                        var addContextDiffRatio = parseFloat(((addContextDiff_count) / addContextAll_nodes.length).toFixed(2));

                        graphFactory.setStats('AddContextIntersectionRatio', addContextIntRatio);
                        graphFactory.setStats('AddContextDifferenceRatio', addContextDiffRatio);

                        graphFactory.setStats('TotalNodesAddContext', addContextAll_nodes.length);
                        graphFactory.setStats('TotalNodesCurrentContext', currentContextAll_nodes.length);

                        addcontext_nodes['intersection'] = addContextInt_nodes;
                        addcontext_nodes['difference'] = addContextDiff_nodes;

                        addcontext_ratio['intersection'] = (addContextIntRatio * 100).toFixed(0);
                        addcontext_ratio['difference'] = (addContextDiffRatio * 100).toFixed(0);
                    }




                    // Number of nodes in the top topic

                    var nodesintoptopic = 0;

                    nodesintoptopic = ((((nodesfirstcom) / totalnodescount).toFixed(2))*100).toFixed(0);

                    nodesintoptopic = parseInt(nodesintoptopic);

                    graphFactory.setStats('NodesInTopTopic', nodesintoptopic);


                    // Graph density (not weighed)

                    let graph_density;

                    if ((totalnodescount - 1) == 0 || totalnodescount == 0) { graph_density = 0 } else {
                        graph_density  = ((totaledgescount) / ((totalnodescount)*(totalnodescount - 1))).toFixed(3);
                    }

                    graph_density = parseFloat(graph_density);

                    graphFactory.setStats('GraphDensity', graph_density);


                    // Average graph degree

                    let average_degree = (totaledgescount / totalnodescount).toFixed(2);

                    average_degree = parseFloat(average_degree);

                    graphFactory.setStats('AverageDegree', average_degree);


                    // Percentage of nodes in the top connected component of the graph (giant component)

                    top_components = _.sortBy(top_components, 'length').reverse();

                    var num_nodes_component = ((top_components[0].nodes().length / totalnodescount).toFixed(2)*100).toFixed(0);

                    num_nodes_component = parseFloat(num_nodes_component);

                    graphFactory.setStats('NodesInTopComponent', num_nodes_component);
   

                    // Let's calculate the graph diversity (polysingulairty) score
                    var diversity_score = 0;
             
                    // TODO add degree distribution to the indexOf maybe through entropy of degree by nodes

                    var bc_dist = [];

                    // For every top BC node
                    console.log("All the different communities in the graph:")
                    console.log(all_communities);


                    for (var bcid in topInfluenceNodes) {

                        if (topInfluenceNodes[bcid].bc > 0) {

                            for (var comid in all_communities) {
                                // Open first community

                                    //  For every node in that community

                                        for (var ccnodes in all_communities[comid].nodes) {

                                            // If the node's ID equals to the current BC node, add the marker
                                            if (all_communities[comid].nodes[ccnodes].id == topInfluenceNodes[bcid].key) {
                                                bc_dist.push(all_communities[comid].id);
                                            }

                                        }
                            }
                        }

                    }

                    console.log('Distribution of top nodes across all the communities: ')
                    console.log(bc_dist);

                    var bc_dist_string = bc_dist.join('');

                    // Max entropy for a set of 4 elements is 2

                    var bc_entropy = shannon(bc_dist_string);

                    bc_entropy = parseFloat(bc_entropy);

                    graphFactory.setStats('BCEntropy', bc_entropy);

                    // TODO fix bug for two-digit communities

                    console.log('BC Entropy: ' + bc_entropy);



                    // Calculate polysingularity score
                    diversity_score = polysingularity().calculateScore(modularity_score, nodesintoptopic, bc_entropy, topInfluenceNodes, num_nodes_component);

                    graphFactory.setStats('PolysingularityScore', diversity_score);


                    // Create insight clusters from the community nodes
                    graphFactory.createInsightClusters(topN_communities);

                    let insightClusters = graphFactory.getInsightClusters();

                    // Populate Analytics Panel with insight recommendation
                    analyticsPanel.populateRecommendation(insightClusters);

                    // Populate entries field
                    addEntryForm().populateRecommendation(insightClusters);

                    // Populate the insight cluster
                    statementsView().populateRecommendation(insightClusters);

                    // Generate a nice fixed patterns for the communities
                    let palette = generatePalette(userFactory.getGraphPalette(), max_community_number, userFactory.getBackground());

                    // Color the topic boxes in the Analytics panel
                    analyticsPanel.colorTopics(palette);    

                    // Color the nodes in the graph to communities colors
                    // TODO this function also counts addcontext nodes, should be taken out
                    graph().colorNodesCommunities(palette);

                    // How influential nodes are dispersed among the communities
                    let influence_dispersal = ((bc_entropy/2).toFixed(1)*100).toFixed(0);

                    influence_dispersal = parseFloat(influence_dispersal);

                    graphFactory.setStats('InfluenceDispersal', influence_dispersal);


                    // How many communities are there
                    let communities_number = all_communities.length;

                    graphFactory.setStats('NumberOfCommunities', communities_number);


                    // How many connected components are there in the graph?
                    let top_components_length = top_components.length;

                    graphFactory.setStats('NumberOfComponents', top_components_length);


                    // Modularity of the graph 
                    modularity_score = modularity_score.toFixed(2);

                    modularity_score = parseFloat(modularity_score);

                    graphFactory.setStats('ModularityScore', modularity_score);

                    
                    // Populate Analytics Panel data

                    analyticsPanel.populateStats(totalnodescount, graph_density, average_degree, diversity_score, modularity_score, nodesintoptopic, communities_number, num_nodes_component, top_components_length, influence_dispersal);


                    // Populate information about the additional contexts

                    analyticsPanel.populateAddContextInfo(addcontext_nodes, userFactory.getCurrentContext(), userFactory.getAddContext(), addcontext_ratio);


                    // Output all the stats into the console

                    console.log(graphFactory.getAllStats());

                    // Activate buttons for LDA communities
                    analyticsPanel.activateButtons(userFactory.getCurrentUser(), userFactory.getCurrentContext(), louvain_communities, palette);

                    // Set triggers on all the nodes in the Analytics pane
                    analyticsPanel.activateTopNodes();

                }

                // Callback the next function (Sigma refresh and Force Atlas)
                callback();

    }


</script>